package com.marketlive.system.config;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.marketlive.system.config.IConfigurationManager;
import org.marketlive.system.config.IConfigurationBranch;
import org.marketlive.system.config.IConfigurationContext;

import java.util.ResourceBundle;
import java.util.PropertyResourceBundle;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;

import com.marketlive.system.config.bundle.BundleLoader;

/**
 * Implements the <code>IConfigurationManager</code> service.
 */
public class ConfigurationManager implements IConfigurationManager {

  /** Logger. */
  private static Log log = LogFactory.getLog(ConfigurationManager.class);
  /** Branch root name. */
  public static final String ROOT_BRANCH_NAME = "ROOT";
  /** Context file name. */
  public static final String CONTEXT_FILE_NAME = "context.properties";
  /** All context variables start with this. */
  public static final String CONTEXT_KEY_PREFIX = "MARKETLIVE_";
  /** Context site key. */
  public static final String CONTEXT_CLIENT_KEY = "CLIENT";
  /** Context site key. */
  public static final String CONTEXT_SITE_KEY = "SITE";
  /** Context environment key. */
  public static final String CONTEXT_ENVIRONMENT_KEY = "ENVIRONMENT";
  /** Context host key. */
  public static final String CONTEXT_HOST_KEY = "HOST";
  /** Context vm id key. */
  public static final String CONTEXT_VMID_KEY = "VM_ID";
  /** Context vm type key. */
  public static final String CONTEXT_VMTYPE_KEY = "VM_TYPE";
  /** Go secure key. */
  public static final String SECURE_KEY = "system.security.go_secure";
  /** Site home variable. */
  public static final String SITE_HOME = "SITE_HOME";
  /** Config ignore list. */
  public static final String CONFIG_IGNORE_LIST = "CONFIG_IGNORE_LIST";
  /** Config root path. */
  public static String CONFIG_ROOT_PATH = "";
  /** The 'root' branch, also could be known as the trunk. */
  private final ConfigurationBranch root = new ConfigurationBranch(ROOT_BRANCH_NAME);
  /** Context used to determine what parts of the configuration tree to use. */
  private ConfigurationContext context;


  /**
   * Default constructor. Initializes the <code>IConfigurationContext</code>.
   */
  public ConfigurationManager() {
    this.init();
  }

  /**
   * Initializes <code>IConfigurationContext</code>.
   */
  public void init() {
    context = new ConfigurationContext();
    loadContext();
  }

  /**
   * {@inheritDoc}
   */
  public IConfigurationBranch getRoot() {
    return root;
  }

  /**
   * {@inheritDoc}
   */
  public IConfigurationBranch getBranch(String path) {
    return root.getBranch(path, false);
  }

  /**
   * {@inheritDoc}
   */
  public String getAsString(String name) {
    return getRoot().getAsString(name);
  }

  /**
   * {@inheritDoc}
   */
  public int getAsInt(String name) {
    return Integer.parseInt(getAsString(name));
  }

  /**
   * {@inheritDoc}
   */
  public boolean getAsBoolean(String name) {
    return Boolean.parseBoolean(getAsString(name));
  }

  /**
   * {@inheritDoc}
   */
  public boolean getAsBoolean(String name, boolean defaultValue) {
    try {
      return Boolean.parseBoolean(getAsString(name));
    } catch (IllegalArgumentException e) {
      return defaultValue;
    }
  }

  /**
   * {@inheritDoc}
   */
  public IConfigurationContext getContext() {
    return context;
  }

  /**
   * {@inheritDoc}
   */
  public boolean isGoSecure() {
    String property = getAsString(SECURE_KEY);
    return (property != null) ? Boolean.parseBoolean(property) : true;
  }

  /**
   * {@inheritDoc}
   */
  public void registerBundle(String path, ResourceBundle bundle) {
    ConfigurationBranch subBranch = (ConfigurationBranch) root.getBranch(path, true);
    BundleLoader.loadBundle(subBranch, bundle);
  }

  /**
   * {@inheritDoc}
   */
  public String printConfiguration() {
    return ConfigurationToString.toStringBuilder(this);
  }

  /**
   * {@inheritDoc}
   */
  public void init(String configurationRoot) {
    // To change body of implemented methods use File | Settings | File Templates.
  }

  /**
   * {@inheritDoc}
   */
  public boolean isBranchDefined(String name) {
    boolean retVal = false;
    IConfigurationBranch branch = null;
    try {
      branch = getBranch(name);
      if (null != branch) {
        retVal = true;
      }
    } catch (Exception e) {
      // consume exception
      log.debug("Exception" + e);
    }
    return retVal;
  }

  /**
   * Loads the context. The algorithm works as follows: 1) The system properties MARKETLIVE_VM_ID, MARKETLIVE_VM_TYPE,
   * MARKETLIVE_SITE, MARKETLIVE_HOST, MARKETLIVE_ENVIRONMENT are used if they are set 2) System property
   * MARKETLIVE_SITE_HOME is checked to look for the context properties file. If found it is used to set any values not
   * set in step 1) above. It will not override the variables in step 1. 3) A file called context.properties will be
   * looked for in the root of the classpath and used. 4) Defaults are used where applicable. 5) If one of the context
   * parameters is not defined in the above process, overrides based on that dimension will not be applied.
   */
  private void loadContext() {

    // use the system properties if set or make them null if not set.
    context.setClient(System.getProperty(CONTEXT_KEY_PREFIX + CONTEXT_SITE_KEY));
    context.setEnvironment(System.getProperty(CONTEXT_KEY_PREFIX + CONTEXT_ENVIRONMENT_KEY));
    context.setHost(System.getProperty(CONTEXT_KEY_PREFIX + CONTEXT_HOST_KEY));
    context.setVmId(System.getProperty(CONTEXT_KEY_PREFIX + CONTEXT_VMID_KEY));
    context.setVmType(System.getProperty(CONTEXT_KEY_PREFIX + CONTEXT_VMTYPE_KEY));

    // look for the path to the context properties file
    CONFIG_ROOT_PATH = System.getProperty(CONTEXT_KEY_PREFIX + SITE_HOME) + "/conf";
    ResourceBundle bundle = null;
    if (CONFIG_ROOT_PATH != null) {
      bundle = loadBundleFromPath(CONFIG_ROOT_PATH + File.separatorChar + CONTEXT_FILE_NAME);
    }
    if (bundle == null) {
      // look in class path
      bundle = loadBundleFromClassPath();
    }

    if (bundle != null) {
      context.setClient(getPropertyValue(bundle, CONTEXT_SITE_KEY, context.getClient()));
      context.setEnvironment(getPropertyValue(bundle, CONTEXT_ENVIRONMENT_KEY, context.getEnvironment()));
      context.setHost(getPropertyValue(bundle, CONTEXT_HOST_KEY, context.getHost()));
      context.setVmId(getPropertyValue(bundle, CONTEXT_VMID_KEY, context.getVmId()));
      context.setVmType(getPropertyValue(bundle, CONTEXT_VMTYPE_KEY, context.getVmType()));
    }
  }

  /**
   * Loads and returns the bundle or returns null if path is null or file doesn't exist.
   *
   * @param path fully qualified path to resource file
   * @return <code>PropertyResourceBundle</code> if resource found, null otherwise
   */
  private PropertyResourceBundle loadBundleFromPath(String path) {
    if (path == null) {
      log.info("loadBundleFromPath: path is null, not loading context property file.");
      return null;
    }

    PropertyResourceBundle bundle = null;
    try {
      bundle = new PropertyResourceBundle(new BufferedInputStream(new FileInputStream(path)));
    } catch (Exception e) {
      log.info("Could not load property resource bundle from path: " + path);
      bundle = null;
    }
    return bundle;
  }

  /**
   * Returns <code>ResourceBundle</code> using classpath to locate.
   * @return <code>ResourceBundle</code> if found, null otherwise
   */
  private ResourceBundle loadBundleFromClassPath() {
    ResourceBundle bundle = null;
    try {
      bundle = ResourceBundle.getBundle("context");
    } catch (Exception e) {
      log.info("loadBundleFromClassPath: could not load the context from the class path.");
      bundle = null;
    }
    return bundle;
  }

  /**
   * Return the property value from the bundle if present and not already set, Default value otherwise.
   *
   * @param bundle the resource bundle
   * @param key the key from the resource bundle
   * @param defaultValue returned if key wasn't found in the resource bundle
   * @return <code>String</code> value from bundle, or default if already set or not found
   */
  private String getPropertyValue(ResourceBundle bundle, String key, String defaultValue) {

    // if its already set, leave it alone
    if (defaultValue != null) {
      return defaultValue;
    }

    try {
      return bundle.getString(key);
    } catch (Exception e) {
      return defaultValue;
    }
  }
}
